﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using PredictionBuilder.Attributes;
using PredictionBuilder.Enum;
using PredictionBuilder.Model;

namespace PredictionBuilder.Utils
{
    /// <summary>
    /// 表达式帮助类
    /// </summary>
    public class ExpressionHelper
    {
        private readonly static Dictionary<string, MethodInfo> methodCache= new Dictionary<string, MethodInfo>()
        {
            {"StringContains",typeof(string).GetMethod("Contains", new[] { typeof(string) }) },
            {"IEnumerableContains",Type.GetType("System.Collections.Generic.EnumerableExtensions")?.GetMethod("Contains")},
            {"StartsWith",typeof(string).GetMethod("StartsWith", new[] { typeof(string) }) },
            {"EndsWith",typeof(string).GetMethod("EndsWith", new[] { typeof(string) })}
        };
        /// <summary>
        /// 生成形如(a,b,c)=>a.Id==1 这样的表达式
        /// </summary>
        /// <typeparam name="T">委托类型</typeparam>
        /// <param name="mainParameter">主要参数</param>
        /// <param name="operatorType">表达式的操作符</param>
        /// <param name="propertyName">表达式的属性名</param>
        /// <param name="propertyValue">表达式的常量</param>
        /// <param name="parameterOrderType">参数顺序</param>
        /// <param name="parameters">所有的参数(按照委托的泛型的顺序)</param>
        /// <returns></returns>
        /// <exception cref="NotSupportedException"></exception>
        public static Expression<T> CreatePrediction<T>(ParameterExpression mainParameter, OperatorType operatorType, string propertyName, object propertyValue, ParameterOrderType parameterOrderType, params ParameterExpression[] parameters)
            where T : Delegate
        {
            Type t = Nullable.GetUnderlyingType(mainParameter.Type.GetProperty(propertyName).PropertyType) ?? mainParameter.Type.GetProperty(propertyName).PropertyType;
            Expression left;
            Expression right;
            Expression body;
            MethodInfo method;
            if (parameterOrderType == ParameterOrderType.Asc)
            {
                left = Expression.Convert(Expression.PropertyOrField(mainParameter, propertyName), t);

                right = Expression.Constant(propertyValue);//创建常数
            }
            else
            {
                right = Expression.Convert(Expression.PropertyOrField(mainParameter, propertyName), t);
				left = Expression.Constant(propertyValue);//创建常数
            }

            switch (operatorType)
            {
                case OperatorType.Equal:
                    body = Expression.Equal(left, right);
                    break;
                case OperatorType.NotEqual:
                    body = Expression.NotEqual(left, right);
                    break;
                case OperatorType.LessThan:
                    body = Expression.LessThan(left, right);
                    break;
                case OperatorType.LessThanOrEqual:
                    body = Expression.LessThanOrEqual(left, right);
                    break;
                case OperatorType.GreaterThan:
                    body = Expression.GreaterThan(left, right);
                    break;
                case OperatorType.GreaterThanOrEqual:
                    body = Expression.GreaterThanOrEqual(left, right);
                    break;
                case OperatorType.Contains:
                    var propertyValueType = propertyValue.GetType();
                    //method = methodCache["StringContains"];
                    //body = Expression.Call(left, method, right);
                    if (propertyValueType == typeof(string))
                    {
                        method = methodCache["StringContains"];
                        body = Expression.Call(left, method, right);
                    }
                    else
                    {
                        method = propertyValueType.GetMethod("Contains");
                        if(method == null)
                        {
                            throw new NotSupportedException($"{propertyName} can not find method:Contains!!");
                        }
                        else
                        {
                            right = Expression.Convert(Expression.PropertyOrField(mainParameter, propertyName), t);
                            left = Expression.Constant(propertyValue);//创建常数
                            body = Expression.Call(left, method, right);
                        }
                    }

                    break;
                case OperatorType.NotContains:
                    method = methodCache["StringContains"];
                    body = Expression.Not(Expression.Call(left, method, right));
                    break;
                case OperatorType.StartsWith:
                    method = methodCache["StartsWith"];
                    body = Expression.Call(left, method, right);
                    break;
                case OperatorType.NotStartsWith:
                    method = methodCache["StartsWith"];
                    body = Expression.Not(Expression.Call(left, method, right));
                    break;
                case OperatorType.EndsWith:
                    method = methodCache["EndsWith"];
                    body = Expression.Call(left, method, right);
                    break;
                case OperatorType.NotEndsWith:
                    method = methodCache["EndsWith"];
                    body = Expression.Not(Expression.Call(left, method, right));
                    break;
                default:
                    throw new NotSupportedException();
            }
            return Expression.Lambda<T>(body, parameters);
        }

        public static Expression<T> CreatePrediction<T>(ParameterExpression mainParameter, CustomMethodAttribute attribute, string propertyName, object propertyValue, ParameterOrderType parameterOrderType, params ParameterExpression[] parameters)
        {
            var method = attribute.GetCustomMethod(mainParameter.Type.GetProperty(propertyName).PropertyType, propertyValue, parameterOrderType);
            Type t = Nullable.GetUnderlyingType(mainParameter.Type.GetProperty(propertyName).PropertyType) ?? mainParameter.Type.GetProperty(propertyName).PropertyType;
            Expression left;
            Expression right;
            Expression body;
            if (parameterOrderType == ParameterOrderType.Asc)
            {
                left = Expression.Convert(Expression.PropertyOrField(mainParameter, propertyName), t);
                right = Expression.Constant(propertyValue);//创建常数
            }
            else
            {
                right = Expression.Convert(Expression.PropertyOrField(mainParameter, propertyName), t);
                left = Expression.Constant(propertyValue);//创建常数
            }
            body = Expression.Call(null, method, left, right);
            var staticMethodValue = Expression.Constant(attribute.MethodPredictValue);
            switch (attribute.OperatorType)
            {
                case OperatorType.Equal:
                    body = Expression.Equal(body, staticMethodValue);
                    break;
                case OperatorType.NotEqual:
                    body = Expression.NotEqual(body, staticMethodValue);
                    break;
                case OperatorType.LessThan:
                    body = Expression.LessThan(body, staticMethodValue);
                    break;
                case OperatorType.LessThanOrEqual:
                    body = Expression.LessThanOrEqual(body, staticMethodValue);
                    break;
                case OperatorType.GreaterThan:
                    body = Expression.GreaterThan(body, staticMethodValue);
                    break;
                case OperatorType.GreaterThanOrEqual:
                    body = Expression.GreaterThanOrEqual(body, staticMethodValue);
                    break;
                default:
                    throw new NotSupportedException();
            }
            return Expression.Lambda<T>(body, parameters);
        }
        /// <summary>
        /// 使用and拼接表达式
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="orginExp">源表达式</param>
        /// <param name="andExp">拼接的表达式</param>
        /// <returns></returns>
        public static Expression<T> And<T>(Expression<T> orginExp, Expression<T> andExp) where T : Delegate
        {
            if (orginExp == null)
            {
                return andExp;
            }
            if (andExp == null)
            {
                return orginExp;
            }
            return Expression.Lambda<T>(Expression.AndAlso(orginExp.Body, andExp.Body), orginExp.Parameters);
        }
        /// <summary>
        /// 使用or拼接表达式
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="orginExp">源表达式</param>
        /// <param name="orExp">表达式</param>
        /// <returns></returns>
        public static Expression<T> Or<T>(Expression<T> orginExp, Expression<T> orExp) where T : Delegate
        {
            if (orginExp == null )
            {
                return orExp;
            }
            if (orExp == null )
            {
                return orginExp;
            }
            return Expression.Lambda<T>(Expression.OrElse(orginExp.Body, orExp.Body), orginExp.Parameters);
        }
        /// <summary>
        /// 根据枚举值拼接表达式
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="orginExp">源表达式</param>
        /// <param name="joinExp">被拼接的表达式</param>
        /// <param name="connectType">拼接类型</param>
        /// <returns></returns>
        /// <exception cref="NotSupportedException"></exception>
        public static Expression<T> JoinExpression<T>(Expression<T> orginExp, Expression<T> joinExp, ConnectType connectType) where T : Delegate
        {
            switch (connectType)
            {
                case ConnectType.And:
                    return And(orginExp, joinExp);
                case ConnectType.Or:
                    return Or(orginExp, joinExp);
                default:
                    throw new NotSupportedException();
            }
        }
        /// <summary>
        /// 安全转换value的类型
        /// </summary>
        /// <param name="type">转换的目标类型</param>
        /// <param name="value">value原始值</param>
        /// <returns></returns>
        public static object SafeConversion(Type type, object value)
        {
            Type t = Nullable.GetUnderlyingType(type) ?? type;
            object safeValue = (value == null) ? null : Convert.ChangeType(value, t);
            return safeValue;
        }
        /// <summary>
        /// 安全转换value的类型
        /// </summary>
        /// <typeparam name="T">转换的类型</typeparam>
        /// <param name="value">原始值</param>
        /// <returns></returns>
        public static T SafeConversion<T>(object value)
        {
            Type t = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T);
            object safeValue = (value == null) ? null : Convert.ChangeType(value, t);
            return (T)safeValue;
        }

        public Dictionary<Type, string> GetExpressionParameterInfo<T>(params Expression<T>[] expressions) where T : Delegate
        {
            Dictionary<Type, string> keyValuePairs = new Dictionary<Type, string>();
            foreach (var expression in expressions)
            {
                var parameters = expression.Parameters;
                foreach (var parameter in parameters)
                {
                    if (!keyValuePairs.TryGetValue(parameter.Type, out string value))
                    {
                        keyValuePairs.Add(parameter.Type, parameter.Name);
                    }
                    else
                    {
                        throw new InvalidOperationException($"type:{parameter.Type} mapping more than one parameterName!");
                    }
                }
            }
            return keyValuePairs;
        }

        /// <summary>
        /// 根据表达式获取属性的名称
        /// </summary>
        /// <typeparam name="T">实体类</typeparam>
        /// <param name="exp">表达式</param>
        /// <returns></returns>
        public static string GetPropertyName<T>(Expression<Func<T, object>> exp)
        {
            var Name = "";
            var body = exp.Body;
            if (body is UnaryExpression expression)
            {
                Name = ((MemberExpression)expression.Operand).Member.Name;
            }
            else if (body is MemberExpression expression1)
            {
                Name = expression1.Member.Name;
            }
            else if (body is ParameterExpression expression2)
            {
                Name = expression2.Type.Name;
            }
            return Name;
        }

        public static (object LeftFiled, object RightFiled) ResolveExpression<T>(Expression<Func<T, bool>> expression)
        {
            if (expression == null)
            {
                throw new ArgumentNullException(nameof(expression));
            }
            var body = (BinaryExpression)expression.Body;
            var left = GetExpPropertyNameOrValue(body.Left);
            var right = GetExpPropertyNameOrValue(body.Right);
            return (left, right);
        }

        /// <summary>
        /// 根据形似t.id==t.name的表达式 返回（"id","name"）这样的元组
        /// </summary>
        /// <param name="expression">表达式</param>
        /// <returns>字段组成的元组</returns>
        public static object GetExpPropertyNameOrValue(Expression expression)
        {
            object value = "";
            if (expression is UnaryExpression expression2)
            {
                value = ((MemberExpression)expression2.Operand).Member.Name;
            }
            else if (expression is MemberExpression expression1)
            {
                value = expression1.Member.Name;
            }
            else if (expression is ParameterExpression expression3)
            {
                value = expression3.Type.Name;
            }
            else if (expression is ConstantExpression expression4)
            {
                value = expression4.Value;
            }
            return value;
        }
        public static T Test<T>(string str,T t)
        {
            throw new Exception();
        }
        public static T Test<T,T1>(T1 t1, T t)
        {
            throw new Exception();
        }
    }
}
